﻿#include "XMFile.h"
#include "io/ByteBuffer.h"
#include "Util.h"
#include "Flag.h"
#include "lang/String.h"
#include "lang/Math.h"
using namespace yzrilyzr_array;
using namespace yzrilyzr_io;
using namespace yzrilyzr_lang;
namespace yzrilyzr_util{
	ByteArray * READ_MEMCPY_BOUND(ByteBuffer & buffer, int offset, int length, int bound){
		int copy=bound >= offset?(bound - offset):0;
		copy=Math::min(copy, length);
		ByteArray * out=new ByteArray(length);
		u_index oldPos=buffer.position();
		buffer.position(offset);
		buffer.get(*out);
		buffer.position(oldPos);
		memset(out->_array + copy, 0, length - copy);
		return out;
	}
	uint8_t READ_U8_BOUND(ByteBuffer & buffer, int offset, int bound){
		return (((offset) < (bound))?(buffer.get(offset)):0);
	}
	uint16_t READ_U16_BOUND(ByteBuffer & buffer, int offset, int bound){
		return (READ_U8_BOUND(buffer, offset, bound) | (READ_U8_BOUND(buffer, (offset)+1, bound) << 8));
	}
	void getAtPos(ByteBuffer & xmBuffer, ByteArray & dst, int offset){
		u_index pos=xmBuffer.position();
		xmBuffer.position(offset);
		xmBuffer.get(dst);
		xmBuffer.position(pos);
	}
	uint32_t READ_U32_BOUND(ByteBuffer & buffer, int offset, int bound){
		return (READ_U16_BOUND(buffer, offset, bound) | (READ_U16_BOUND(buffer, (offset)+2, bound) << 16));
	}
	std::shared_ptr<XMFile::Module> XMFile::parse(InputStream & inputStream){
		try{
			ByteBuffer & xmBuffer=*ByteBuffer::wrap(Util::read(inputStream));
			ByteArray magic(17);
			xmBuffer.get(magic);
			if(String(magic) != "Extended Module: ") throw new IOException("Not XM");
			std::shared_ptr<Module> modp=std::make_shared<Module>();
			Module & mod=*modp;
			ByteArray name(20);
			xmBuffer.get(name);
			mod.name=name;
			int i1=xmBuffer.get();
			if(i1 != 0x1A) throw new IOException("Not 1A");
			ByteArray trackerName(20);
			xmBuffer.get(trackerName);
			mod.trackerName=trackerName;
			int xmVersion1=xmBuffer.get();
			int xmVersion2=xmBuffer.get();
			if(xmVersion1 != 4 || xmVersion2 != 1) throw new IOException("XM version not 1.04");
			//
			xmBuffer.order(ByteOrder::LITTLE_ENDIAN);
			int headerLength=xmBuffer.getInt();
			mod.length=xmBuffer.getShort();
			mod.restart_position=xmBuffer.getShort();
			mod.num_channels=xmBuffer.getShort();
			mod.num_patterns=xmBuffer.getShort();
			mod.num_instruments=xmBuffer.getShort();
			mod.patterns.resize(mod.num_patterns);
			mod.instruments.resize(mod.num_instruments);
			int flagsFreq=xmBuffer.getShort();
			mod.frequency_type=(flagsFreq & 1) == 1?XM_LINEAR_FREQUENCIES:XM_AMIGA_FREQUENCIES;
			mod.tempo=xmBuffer.getShort();
			mod.bpm=xmBuffer.getShort();
			xmBuffer.get(mod.pattern_table);
			xmBuffer.position(60 + headerLength);
			/* Read patterns */
			int offset=60 + headerLength;
			for(u_index i=0;i < mod.num_patterns;++i){
				Pattern & pat=mod.patterns[i];
				pat.num_rows=xmBuffer.getShort(offset + 5);
				int packed_patterndata_size=xmBuffer.getShort(offset + 7);
				pat.slots.resize(mod.num_channels * pat.num_rows);
				int patternHeadLength=xmBuffer.getInt(offset);
				offset+=patternHeadLength;
				if(packed_patterndata_size == 0){
					std::cout << "No pattern data is present" << std::endl;
				} else{
					/* This isn't your typical for loop */
					for(u_index j=0, k=0;j < packed_patterndata_size;++k){
						int note=xmBuffer.get(offset + j) & 0xff;
						PatternSlot & slot=pat.slots[k];
						//=new PatternSlot();
						if((note & (1 << 7)) > 0){
							/* MSB is set, this is a compressed packet */
							++j;
							if((note & (1)) > 0){
								/* Note follows */
								slot.note=xmBuffer.get(offset + j) & 0xff;
								++j;
							} else{
								slot.note=0;
							}
							if((note & (1 << 1)) > 0){
								/* Instrument follows */
								slot.instrument=xmBuffer.get(offset + j) & 0xff;
								++j;
							} else{
								slot.instrument=0;
							}
							if((note & (1 << 2)) > 0){
								/* Volume column follows */
								slot.volume_column=xmBuffer.get(offset + j) & 0xff;
								++j;
							} else{
								slot.volume_column=0;
							}
							if((note & (1 << 3)) > 0){
								/* Effect follows */
								slot.effect_type=xmBuffer.get(offset + j) & 0xff;
								++j;
							} else{
								slot.effect_type=0;
							}
							if((note & (1 << 4)) > 0){
								/* Effect parameter follows */
								slot.effect_param=xmBuffer.get(offset + j) & 0xff;
								++j;
							} else{
								slot.effect_param=0;
							}
						} else{
							/* Uncompressed packet */
							slot.note=note;
							slot.instrument=xmBuffer.get(offset + j + 1) & 0xff;
							slot.volume_column=xmBuffer.get(offset + j + 2) & 0xff;
							slot.effect_type=xmBuffer.get(offset + j + 3) & 0xff;
							slot.effect_param=xmBuffer.get(offset + j + 4) & 0xff;
							j+=5;
						}
					}
				}
				offset+=packed_patterndata_size;
			}

			/* Read instruments */
			for(u_index i=0;i < mod.num_instruments;++i){
				Instrument & instr=mod.instruments[i];
		/* Original FT2 would load instruments with a direct read into the
		   instrument data structure that was previously zeroed. This means
		   that if the declared length was less than INSTRUMENT_HEADER_LENGTH,
		   all excess data would be zeroed. This is used by the XM compressor
		   BoobieSqueezer. To implement this, bound all reads to the header size. */
				int ins_header_size=xmBuffer.getInt(offset);
				if(ins_header_size == 0 || ins_header_size > INSTRUMENT_HEADER_LENGTH)
					ins_header_size=INSTRUMENT_HEADER_LENGTH;
				ByteArray * instrName=READ_MEMCPY_BOUND(xmBuffer, offset + 4, INSTRUMENT_NAME_LENGTH, offset +
														ins_header_size);
				instr.name=*instrName;
				instr.num_samples=READ_U16_BOUND(xmBuffer, offset + 27, offset + ins_header_size);
				if(instr.num_samples > 0){
					/* Read extra header properties */
					ByteArray * sampleOfNotes=READ_MEMCPY_BOUND(xmBuffer, offset + 33, NUM_NOTES, offset +
																ins_header_size);
					instr.sample_of_notes=sampleOfNotes;
					instr.volume_envelope.num_points=READ_U8_BOUND(xmBuffer, offset + 225, offset +
																   ins_header_size);
					if(instr.volume_envelope.num_points > NUM_ENVELOPE_POINTS)
						instr.volume_envelope.num_points=NUM_ENVELOPE_POINTS;
					instr.panning_envelope.num_points=READ_U8_BOUND(xmBuffer, offset + 226, offset +
																	ins_header_size);
					if(instr.panning_envelope.num_points > NUM_ENVELOPE_POINTS)
						instr.panning_envelope.num_points=NUM_ENVELOPE_POINTS;
					instr.volume_envelope.points.resize(instr.volume_envelope.num_points);
					for(u_index j=0;j < instr.volume_envelope.num_points;++j){
						instr.volume_envelope.points[j].frame=READ_U16_BOUND(xmBuffer, offset +
																			 129 +
																			 4 * j, offset +
																			 ins_header_size);
						instr.volume_envelope.points[j].value=READ_U16_BOUND(xmBuffer, offset +
																			 129 +
																			 4 * j +
																			 2, offset +
																			 ins_header_size);
					}
					instr.panning_envelope.points.resize(instr.panning_envelope.num_points);
					for(u_index j=0;j < instr.panning_envelope.num_points;++j){
						instr.panning_envelope.points[j].frame=READ_U16_BOUND(xmBuffer, offset +
																			  177 +
																			  4 * j, offset +
																			  ins_header_size);
						instr.panning_envelope.points[j].value=READ_U16_BOUND(xmBuffer, offset +
																			  177 +
																			  4 * j +
																			  2,
																			  offset +
																			  ins_header_size);
					}
					instr.volume_envelope.sustain_point=READ_U8_BOUND(xmBuffer, offset + 227, offset +
																	  ins_header_size);
					instr.volume_envelope.loop_start_point=READ_U8_BOUND(xmBuffer, offset + 228, offset +
																		 ins_header_size);
					instr.volume_envelope.loop_end_point=READ_U8_BOUND(xmBuffer, offset + 229, offset +
																	   ins_header_size);
					instr.panning_envelope.sustain_point=READ_U8_BOUND(xmBuffer, offset + 230, offset +
																	   ins_header_size);
					instr.panning_envelope.loop_start_point=READ_U8_BOUND(xmBuffer, offset +
																		  231, offset +
																		  ins_header_size);
					instr.panning_envelope.loop_end_point=READ_U8_BOUND(xmBuffer, offset + 232, offset +
																		ins_header_size);
	// Fix broken modules with loop points outside of defined points
					if(instr.volume_envelope.num_points > 0){
						instr.volume_envelope.loop_start_point=
							Math::min(instr.volume_envelope.loop_start_point, instr.volume_envelope.num_points -
									 1);
						instr.volume_envelope.loop_end_point=
							Math::min(instr.volume_envelope.loop_end_point, instr.volume_envelope.num_points -
									 1);
					}
					if(instr.panning_envelope.num_points > 0){
						instr.panning_envelope.loop_start_point=
							Math::min(instr.panning_envelope.loop_start_point, instr.panning_envelope.num_points -
									 1);
						instr.panning_envelope.loop_end_point=
							Math::min(instr.panning_envelope.loop_end_point, instr.panning_envelope.num_points -
									 1);
					}
					Flag flag(READ_U8_BOUND(xmBuffer, offset + 233, offset + ins_header_size));
					instr.volume_envelope.enabled=flag.has(1);
					instr.volume_envelope.sustain_enabled=flag.has(1 << 1);
					instr.volume_envelope.loop_enabled=flag.has(1 << 2);
					Flag flag1(READ_U8_BOUND(xmBuffer, offset + 234, offset + ins_header_size));
					instr.panning_envelope.enabled=flag1.has(1 << 0);
					instr.panning_envelope.sustain_enabled=flag1.has(1 << 1);
					instr.panning_envelope.loop_enabled=flag1.has(1 << 2);
					instr.vibrato_type=READ_U8_BOUND(xmBuffer, offset + 235, offset + ins_header_size);
					if(instr.vibrato_type == 2){
						instr.vibrato_type=1;
					} else if(instr.vibrato_type == 1){
						instr.vibrato_type=2;
					}
					instr.vibrato_sweep=READ_U8_BOUND(xmBuffer, offset + 236, offset + ins_header_size);
					instr.vibrato_depth=READ_U8_BOUND(xmBuffer, offset + 237, offset + ins_header_size);
					instr.vibrato_rate=READ_U8_BOUND(xmBuffer, offset + 238, offset + ins_header_size);
					instr.volume_fadeout=READ_U16_BOUND(xmBuffer, offset + 239, offset + ins_header_size);
					instr.samples.resize(instr.num_samples);
				} else{
					instr.samples.clear();
				}

				/* Instrument header size */
				offset+=ins_header_size;
				for(u_index j=0;j < instr.num_samples;++j){
					/* Read sample header */
					SampleData & sample=instr.samples[j];
					sample.length=xmBuffer.getInt(offset);
					sample.loop_start=xmBuffer.getInt(offset + 4);
					sample.loop_length=xmBuffer.getInt(offset + 8);
					sample.loop_end=sample.loop_start + sample.loop_length;
					sample.volume=(float)(xmBuffer.get(offset + 12) & 0xff) / (float)0x40;
					sample.finetune=xmBuffer.get(offset + 13);

					/* Fix invalid loop definitions */
					if(sample.loop_start > sample.length)
						sample.loop_start=sample.length;
					if(sample.loop_end > sample.length)
						sample.loop_end=sample.length;
					sample.loop_length=sample.loop_end - sample.loop_start;
					int flags=xmBuffer.get(offset + 14) & 0xff;
					if((flags & 3) == 0 || sample.loop_length == 0){
						sample.loop_type=XM_NO_LOOP;
					} else if((flags & 3) == 1){
						sample.loop_type=XM_FORWARD_LOOP;
					} else{
						sample.loop_type=XM_PING_PONG_LOOP;
					}
					sample.bits=(flags & (1 << 4)) > 0?16:8;
					sample.panning=(float)(xmBuffer.get(offset + 15) & 0xff) / (float)0xFF;
					sample.relative_note=xmBuffer.get(offset + 16);
					ByteArray sampleName(SAMPLE_NAME_LENGTH);
					getAtPos(xmBuffer, sampleName, offset + 18);
					sample.name=sampleName;
					sample.data8=ByteArray(sample.length);
					sample.data16=ShortArray(sample.length);
					if(sample.bits == 16){
						sample.loop_start>>=1;
						sample.loop_length>>=1;
						sample.loop_end>>=1;
						sample.length>>=1;
					}

			/* Notice that, even if there's a "sample header size" in the
			   instrument header, that value seems ignored, and might even
			   be wrong in some corrupted modules. */
					offset+=40;
				}
				for(u_index j=0;j < instr.num_samples;++j){
					/* Read sample data */
					SampleData & sample=instr.samples[j];
					int length=sample.length;
					if(sample.bits == 16){
						int v=0;
						for(u_index k=0;k < length;++k){
							v=v + xmBuffer.getShort(offset + (k << 1));
							sample.data16[k]=(int16_t)v;
						}
						offset+=sample.length << 1;
					} else{
						int v=0;
						for(u_index k=0;k < length;++k){
							v=v + xmBuffer.get(offset + k);
							sample.data8[k]=(int8_t)v;
						}
						offset+=sample.length;
					}
				}
			}
			return modp;
		} catch(Exception e){
			std::cout << e.what() << std::endl;
			return nullptr;
		}
	}
}